#include "llvm/CodeGen/OptSched/list_sched/list_sched.h"
#include "llvm/CodeGen/OptSched/generic/logger.h"
#include "llvm/CodeGen/OptSched/generic/stats.h"
#include "llvm/CodeGen/OptSched/basic/ready_list.h"
#include "llvm/CodeGen/OptSched/basic/data_dep.h"
#include "llvm/CodeGen/OptSched/sched_region/sched_region.h"

namespace opt_sched {

ListScheduler::ListScheduler(DataDepGraph* dataDepGraph,
                             MachineModel* machMdl,
                             InstCount schedUprBound,
                             LISTSCHED_HEURISTIC mainHurstc)
    : ConstrainedScheduler(dataDepGraph, machMdl, schedUprBound) {
  crntSched_ = NULL;

  ReadyList::Priorities prirts;

  switch (mainHurstc) {
    case LSH_CP:
      prirts.criticalPath = true;
      prirts.nodeID = true;
      break;
    case LSH_FSO:
      prirts.fileSchedOrder = true;
      prirts.criticalPath = false;
      prirts.nodeID = false;
      break;
  }

  prirts.successorCount = false;
  prirts.latencySum = false;
  prirts.accountForRsrcs = false;

  rdyLst_ = new ReadyList(dataDepGraph_, prirts);
  if (rdyLst_ == NULL) Logger::Fatal("Out of memory.");
}

ListScheduler::~ListScheduler() {
  delete rdyLst_;
}

FUNC_RESULT ListScheduler::FindSchedule(InstSchedule* sched, SchedRegion* rgn) {
  int stallCnt = 0;

  crntSched_ = sched;
  rgn_ = rgn;
  
  Initialize_();

  while (!IsSchedComplete_()) {
    UpdtRdyLst_(crntCycleNum_, crntSlotNum_);
    rdyLst_->ResetIterator();

    SchedInstruction* inst = NULL;
    bool legalInst = false;
    int lgltyChkCnt = 0;
    while (!legalInst) {
      lgltyChkCnt++;
      inst = rdyLst_->GetNextPriorityInst();
      legalInst = ChkInstLglty_(inst);
    }

    #ifdef IS_DEBUG_MODEL
      Logger::Info("Legality checks made: %d", lgltyChkCnt);
      Stats::legalListSchedulerInstructionHits++;
      Stats::illegalListSchedulerInstructionHits += (lgltyChkCnt - 1);
    #endif

    InstCount instNum;
    // If the ready list is empty.
    if (inst == NULL) {
      instNum = SCHD_STALL;
      stallCnt++;
//      Logger::Info("Scheduling a stall");
      if (stallCnt > dataDepGraph_->GetMaxLtncy())
        return RES_ERROR;
    } else {
      stallCnt = 0;
      instNum = inst->GetNum();
//    Logger::Info("Scheduling inst %d", instNum);
      SchdulInst_(inst, crntCycleNum_);
      inst->Schedule(crntCycleNum_, crntSlotNum_);
      rgn_->SchdulInst(inst, crntCycleNum_, crntSlotNum_, false);
      DoRsrvSlots_(inst);
      rdyLst_->RemoveNextPriorityInst();
      UpdtSlotAvlblty_(inst);
    }

    //if (inst && machMdl_->IsRealInst(inst->GetInstType())) {
    crntSched_->AppendInst(instNum);
    bool cycleAdvanced = MovToNxtSlot_(inst);
    if (cycleAdvanced) InitNewCycle_();
    //}
  }

  #ifdef IS_DEBUG_SCHED
    crntSched_->Print(Logger::GetLogStream(), " ");
  #endif

  return RES_SUCCESS;
}

void ListScheduler::UpdtRdyLst_(InstCount cycleNum, int slotNum) {
  InstCount prevCycleNum = cycleNum - 1;
  LinkedList<SchedInstruction>* lst1 = NULL;
  LinkedList<SchedInstruction>* lst2 = frstRdyLstPerCycle_[cycleNum];

  if (slotNum == 0 && prevCycleNum >= 0) {
    // If at the begining of a new cycle other than the very first cycle,
    // then we also have to include the instructions that might have become
    // ready in the previous cycle due to a zero latency of the instruction
    // scheduled in the very last slot of that cycle [GOS 9.8.02].
    lst1 = frstRdyLstPerCycle_[prevCycleNum];

    if (lst1 != NULL) {
      rdyLst_->AddList(lst1);
      lst1->Reset();
      CleanupCycle_(prevCycleNum);
    }
  }

  if (lst2 != NULL) {
    rdyLst_->AddList(lst2);
    lst2->Reset();
  }
}

} // end namespace opt_sched
